home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / aj < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  61.2 KB  |  1,749 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) aj.gawk 3.2 97/07/29
  3. # 92/03/09 John DuBois (john@armory.com)
  4. # 92/06/29 Ignore children of sdd and mscreen.  Now uses awk instead of egrep.
  5. # 93/10/17 Ignore children of telnetd & scoterm
  6. # 94/02/25 Added IGNOREPROCS and .ajrc file
  7. # 94/03/19 Added command line args.  Deal with defunct procs.
  8. # 94/03/20 Added ability to run on specific users, and i and I options.
  9. # 94/03/21 filbo@armory.com: handle "old" processes ("Mar 20" instead of time)
  10. # 94/03/28 john@armory.com: Converted to gawk script (use gawk for systime).
  11. # 95/08/02 Everest port: make ps input come from /dev/null, to work around
  12. #          gawk+protlib bugs; added u option; work with Everest style times
  13. #          (Mmm-dd); added login to default list of ignored parents.
  14. # 96/05/25 3.0 Major cleanup.  Use getPS().
  15. # 96/05/28 Special meaning of '.' for -a option.  More detailed output for -u.
  16. #          Sort by user name.
  17. # 96/11/13 Added q option.
  18. # 96/12/06 Added C option.
  19. # 97/05/21 Make . turn off -q.  Added ntTU options, @ user name option,
  20. #          make user names given with . or @ be not-users
  21. # 97/07/29 Get correct user name if USER not set
  22.  
  23. BEGIN {
  24.     Name = "aj" 
  25.     Usage = \
  26.     "Usage: " Name " [-AcdhHiIntTUuq] [-x<debug-level>] [-a<altusers>]\n"\
  27.     "          [-p<ignoreprocs>] [-P<parents>] [-C<columns>] [user ...]"
  28.     rcFile = ".ajrc"
  29.     ARGC = Opts(Name,Usage,"a:cdiIp:P:qw<HhtTuUx>",0,
  30.     "~/" rcFile ":$UHOME/" rcFile,
  31.     "ALTUSERS,COUNTDEFUNCT,DISPLAYDEFUNCT,IDLETIME,IDLESINCE,"\
  32.     "IGNOREPROCS,PARENTS,QUICK,WIDTH,HEADER",7,"n",0,"","t,T")
  33.     # If anything is added to this, change the help message too.
  34.     defParents = "sdd|mscreen|telnetd|scoterm|login"
  35.     if ("h" in Options) {
  36.     printf \
  37. "%s: print all interesting jobs being run by a user.\n"\
  38. "%s\n"\
  39. "%s prints all processes that the user is running that are attached to any\n"\
  40. "terminal, with the exception of sdd, mscreen, telnetd, scoterm, login, and\n"\
  41. "their immediate children (these are generally shells, daemons, or other\n"\
  42. "uninteresting processes).  %s identifies as owned by the user any\n"\
  43. "processes whose owner is the same as the user ID given by the environment\n"\
  44. "variable USER, or if it not set, the owner of the current process.  If\n"\
  45. "any user names are given on the command line (other than with the -a\n"\
  46. "option), %s reports on the named users only.  If the user name '.' is\n"\
  47. "given, all users are reported on.  If the user name '@' is given, all users\n"\
  48. "who have a login shell listed in /etc/shells are reported on.  If either\n"\
  49. "'.' or '@' is given, any other user names given on the command line are\n"\
  50. "taken to be the names of users who should not be reported on.\n"\
  51. "Options:\n"\
  52. "Some of the following options can also be set by assigning values to\n"\
  53. "variables either in the environment or in a configuration file named %s,\n"\
  54. "which is searched for in the invoking user's home directory and in the\n"\
  55. "directory specified by the environment variable UHOME, if it is set.  If\n"\
  56. "both files exist, values set in the former take precedence.  Variables set\n"\
  57. "in the environment take precedence over both, and options given on the\n"\
  58. "command line have the highest precedence of all.  Variables are assigned\n"\
  59. "to with the syntax:  varname=value  or in the case of flags, by simply\n"\
  60. "putting the indicated variable name in the file without a value.  The\n"\
  61. "names of variables that may be set in the environment are followed by\n"\
  62. "\",e\".  Variable names are given in parentheses in the option\n"\
  63. "descriptions.\n"\
  64. "-a<altusers>: <altusers> is a comma-separated list of users to report on\n"\
  65. "    in addition to the user running %s.  (ALTUSERS,e)\n"\
  66. "-c: Print a count of matching defunct processes.  (COUNTDEFUNCT,e)\n"\
  67. "-d: Show defunct processes.  By default, they are ignored.  Since TTY\n"\
  68. "    information is not available for defunct processes, proceses which\n"\
  69. "    were not attached to a TTY may be displayed.  (DISPLAYDEFUNCT,e)\n"\
  70. "-h: Print this help.\n"\
  71. "-H: Print a header.  (HEADER)\n"\
  72. "-i, -I: In the output, replace the process' start time with the time the\n"\
  73. "    user controlling the process has been idle (IDLETIME,e) or the time\n"\
  74. "    the user went idle (IDLESINCE,e), respectively.  The difference\n"\
  75. "    between -i and -I is that if e.g. nothing has been read from the user\n"\
  76. "    for 5 minutes, and it is now 10:33:16, -i would show 00:05:00 while -I\n"\
  77. "    would show 10:28:16.  In both cases, the idle time is determined by\n"\
  78. "    the last time any input was read from the process' controlling TTY,\n"\
  79. "    which is not a perfect indication of idle time.\n"\
  80. "-p<ignoreprocs>, -P<parents>: Ignore any process whose name matches the\n"\
  81. "    given pattern.  In the case of -P, the children of matching processes\n"\
  82. "    are also ignored.  There is no default for <ignoreprocs>.  The default\n"\
  83. "    for <parents> is \"%s\".\n"\
  84. "    The values given with -p and -P should be egrep(C)-style patterns. \n"\
  85. "    The version of the process name that is compared is the last component\n"\
  86. "    of the the path that the process' argv[0] is set to (usually, the name\n"\
  87. "    the process was invoked by).  The pattern is implicitely anchored at\n"\
  88. "    the start and end.  (IGNOREPROCS,e and PARENTS,e).\n"\
  89. "-u: Show unignored processes along with information about them\n"\
  90. "    (particularly the names of their parents) that can be used to ignore\n"\
  91. "    them.\n"\
  92. "-T: Do not exclude processes that are not attached to a TTY.\n"\
  93. "-t: Show all processes attached to TTYs.  Equivalent to '-p "" -P "" .' \n"\
  94. "-U: Show \"user processes\": equivalent to '-T -p "" -P "" @ root' \n"\
  95. "-q: Quick operation.  %s normally gathers information about all processes,\n"\
  96. "    not just those owned by the invoking user (and the altusers), because\n"\
  97. "    it needs to know the names of the parents of processes, and the parents\n"\
  98. "    may not be owned by any of those users. This may take an annoyingly\n"\
  99. "    long time on systems that are slow or have a very large number of\n"\
  100. "    processes running.  If -q is given, information is only gathered for\n"\
  101. "    processed owned by the specified users and for root, since root is\n"\
  102. "    usually the owner of any other processes of interest.  -q is turned off\n"\
  103. "    if \".\" or \"@\" is given in the user list.  (QUICK)\n"\
  104. "-w<width>: Truncate output lines so that they are no more than <columns>\n"\
  105. "    characters long.  If 0 is given, output lines are not truncated.\n"\
  106. "    Normally, output is truncated to a number of columns appropriate to\n"\
  107. "    the terminal it is run from so that lines will not wrap.  (WIDTH)\n"\
  108. "-n: Do not read values from any configuration files or the environment.\n"\
  109. "-x<debug-level>: Print debugging info.  <debug-level> is an integer from\n"\
  110. "    1 to 9.  Higher levels produce more detailed debugging information.\n",
  111.     Name,Usage,Name,Name,Name,rcFile,Name,defParents,Name
  112.     exit 0
  113.     }
  114.     Debug = ("x" in Options) ? Options["x"] : 0
  115.     if ((Err = ExclusiveOptions("i,I,u",Options)) != "") {
  116.     printf "Error: %s\n",Err > "/dev/stderr"
  117.     Err = 1
  118.     exit(1)
  119.     }
  120.     DisplayDefunct = "d" in Options
  121.     if ("p" in Options)
  122.     IgnoreProcs = "^(" Options["p"] ")$"
  123.     if ("P" in Options)
  124.     ParentPat = Options["P"]
  125.     else
  126.     ParentPat = defParents
  127.     ParentPat = "^(" ParentPat ")$"
  128.  
  129.     if ("t" in Options) {
  130.     ParentPat = IgnoreProcs = ""
  131.     ARGV[ARGC++] = "."
  132.     }
  133.     if ("U" in Options) {
  134.     ParentPat = IgnoreProcs = ""
  135.     ARGV[ARGC++] = "@"
  136.     ARGV[ARGC++] = "root"
  137.     }
  138.  
  139.     # Determine whose processes to pay attention to.
  140.     if (ARGC > 1) {
  141.     for (i = 1; i < ARGC; i++)
  142.         Users[ARGV[i]]
  143.     if ("." in Users || "@" in Users) {
  144.         for (user in Users)
  145.         if (user != "." && user != "@") {
  146.             notUsers[user]
  147.             delete Users[user]
  148.         }
  149.     }
  150.     }
  151.     else {
  152.     if ("USER" in ENVIRON)
  153.         Users[ENVIRON["USER"]]
  154.     else {
  155.         id(IDs)
  156.         Users[IDs["user"]]
  157.     }
  158.     if ("a" in Options)
  159.         MakeSet(Users,Options["a"],",")  
  160.     }
  161.     ttyOnly = !("T" in Options || "U" in Options)
  162.     if ("q" in Options && !("." in Users) && !("@" in Users)) {
  163.     UserList = "-uroot," set2list(Users,",")
  164.     if (Debug)
  165.         print "User list: " UserList
  166.     }
  167.     if ("@" in Users) {
  168.     split("",Users)        # empty Users[]
  169.     if (!makeShellUser(Users)) {
  170.         print Name \
  171.         ": Error reading /etc/shells or /etc/passwd" > "/dev/stderr"
  172.         exit 1
  173.     }
  174.     }
  175.  
  176.     fWanted = "UID,TTY,CMD,PPID,TIME,ARGS"
  177.     # Must deal with all processes, since we need to know the names of parents,
  178.     # which may be owned by a UID that we are not otherwise interested in.
  179.     if (Debug) {
  180.     printf "Starting ps at %s\n",strftime("%T")
  181.     if (Debug > 3)    # Only used if debugging is at this level
  182.         fWanted = fWanted ",LINE"
  183.     }
  184.     if (!ShowIdle)    # STIME replaced with idle time if showing idle time
  185.     fWanted = fWanted ",STIME"
  186.     if ((nProc = getPS(PIDs,Procs,fWanted,Children,Debug > 4,UserList)) < 0) {
  187.     printf "ps failed.  Exiting.\n" > "/dev/stderr"
  188.     exit 1
  189.     }
  190.     if (Debug)
  191.     printf "Done with ps at %s\n",strftime("%T")
  192.  
  193.     getline PID < "/dev/pid"
  194.     IgnorePIDs[psPID = PIDs["ps"]]    # Ignore the ps
  195.     IgnorePIDs[PID]            # Ignore this program
  196.     IgnorePPIDs[PID]            # Ignore the shell that ran this program
  197.     IgnorePPIDs[1]            # Ignore children of init
  198.  
  199.     NumDefunct = Mangle(PIDs,Procs,ParentPat,IgnoreProcs,DisplayDefunct,Users,
  200.     notUsers,Debug,IgnorePPIDs,GoodPIDs,TTYs,ttyOnly)
  201.  
  202.     if (Debug) {
  203.     printf "Debug=%s\nIgnoreProcs=%s\nParentPat=%s\n",
  204.     Debug,IgnoreProcs,ParentPat > "/dev/stderr"
  205.     printf "DisplayDefunct=%s\n",DisplayDefunct > "/dev/stderr"
  206.     printf "NUser=%s\nNumber of procs: %d\n",NUser,nProc > "/dev/stderr"
  207.     printf "ps pid: %d\nthis process pid: %d\n",psPID,PID > "/dev/stderr"
  208.     printf "Number of ttys to stat: %d\n",NumElem(TTYs) > "/dev/stderr"
  209.     printf "Number of defunct processes: %d\n",NumDefunct > "/dev/stderr"
  210.     printf "Number of processes to be displayed: %d\n",NumElem(GoodPIDs)\
  211.     > "/dev/stderr"
  212.     }
  213.     if ("w" in Options) {
  214.         cols = Options["w"]
  215.         HeadTailInit(-1,cols ? cols : -1,0,0)
  216.     }
  217.     else
  218.     HeadTailInit(-1)
  219.     PrintResults("H" in Options,"I" in Options,"i" in Options,"u" in Options,
  220.     "c" in Options,NumDefunct,TTYs,GoodPIDs,Procs,Debug)
  221.     if (Debug)
  222.     printf "Processing completed at %s\n",strftime("%T")
  223. }
  224.  
  225. # Generate list of PPIDs to ignore, get list to TTYs to stat,
  226. # and record set of processes to be displayed.
  227. # Input vars:
  228. # PIDs[]: pids of processes in Procs[].
  229. # Procs[]: process data.
  230. # ParentPat: process name pattern for processes to ignore along with children.
  231. # IgnoreProcs: process name pattern for processes to ignore.
  232. # DisplayDefunct: true if defunct processes should be displayed.
  233. # Users[]: Set of users whose processes should be scanned for.
  234. # Output vars:
  235. # IgnorePPIDs[]: process IDs of processes that matched ParentPat.
  236. # GoodPIDs[]: set of PIDs of processes to display.  The value of each element
  237. # is the name of the user who owns the process, for use as a sort key.
  238. # TTYs[]: All ttys that procs in GoodPIDs had as controlling TTY.
  239. # Return value: number of defunct processes.
  240. function Mangle(PIDs,Procs,ParentPat,IgnoreProcs,DisplayDefunct,Users,
  241. notUsers,Debug,IgnorePPIDs,GoodPIDs,TTYs,ttyOnly,
  242. procname,tty,pid,allUsers,User) {
  243.     allUsers = "." in Users
  244.     for (pid in PIDs) {
  245.     if (pid == "ps")
  246.         continue
  247.     if (Debug > 3)
  248.         printf "Processing: %s\n",Procs[pid,"LINE"] > "/dev/stderr"
  249.     procname = basename(Procs[pid,"CMD"])
  250.     if (ParentPat != "" && procname ~ ParentPat) {
  251.         IgnorePPIDs[pid]
  252.         continue
  253.     }
  254.     # skip processes not attached to tty, IgnoreProcs, IgnorePIDs
  255.     tty = Procs[pid,"TTY"]
  256.     User = Procs[pid,"UID"]
  257.         
  258.     if ((allUsers || User in Users) && !(ttyOnly && (tty == "?") || \
  259.     pid in IgnorePIDs || IgnoreProcs != "" && procname ~ IgnoreProcs ||
  260.     User in notUsers)) {
  261.         if (procname == "<defunct>") {
  262.         NumDefunct++
  263.         if (DisplayDefunct)
  264.             GoodPIDs[pid] = User
  265.         }
  266.         else {
  267.         GoodPIDs[pid] = User
  268.         TTYs[Procs[pid,"TTY"] = canonTTY(tty)]
  269.         }
  270.     }
  271.     }
  272.     return NumDefunct
  273. }
  274.  
  275. function PrintResults(PrintHeader,ShowIdleSince,ShowIdleTime,ShowInfo,
  276. CountDefunct,NumDefunct,TTYs,GoodPIDs,Procs,Debug,
  277. Cmd,CurTime,ttylist,IdleTime,pid,format,Fields,header,ppid,num,k,i) {
  278.     split("UID,PID,PPID,STIME,TTY,TIME,ARGS",Fields,",")
  279.     ShowIdle = ShowIdleTime || ShowIdleSince
  280.     if (ShowIdle && (ttylist = set2list(TTYs," ")) != "") {
  281.     if (ShowIdleTime) {
  282.         Cmd = "cd /dev; exec stat -c' ' -nfna " ttylist
  283.         CurTime = systime()
  284.     }
  285.     else
  286.         Cmd = "cd /dev; exec stat -c' ' -nfnA -t%T " ttylist
  287.     if (Debug)
  288.         print "stat command: " Cmd
  289.     while ((Cmd | getline) == 1) {
  290.         if (Debug > 3)
  291.         print "stat line: " $0
  292.         if (ShowIdleTime)
  293.         IdleTime[$1] = sec2hms(max(CurTime - $2,0))
  294.         else    # IdleSince
  295.         IdleTime[$1] = $2
  296.     }
  297.     close(Cmd)
  298.     }
  299.     if (CountDefunct)
  300.     printf "%d defunct process(es).\n",NumDefunct
  301.     if (ShowInfo) {
  302.     format = "%-8s %5s %-4s %-19s"
  303.     format = format " " format "\n"
  304.     printf format,"User","PID","TTY","Process-name","PUser","PPID","PTTY",
  305.     "Parent-name"
  306.     }
  307.     else if (PrintHeader) {
  308.     header = makePSline(-1,Procs,Fields)
  309.     if (ShowIdleTime)
  310.         sub("STIME","IDLE ",header)
  311.     else if (ShowIdleSince)
  312.         sub("STIME   ","IDLESINC",header)
  313.     ColPrint(header)
  314.     }
  315.     num = qsortArbIndByValue(GoodPIDs,k)
  316.     for (i = 1; i <= num; i++)
  317.     if (!(Procs[pid = k[i],"PPID"] in IgnorePPIDs)) {
  318.         if (ShowInfo) {
  319.         ppid = Procs[pid,"PPID"]
  320.         printf format,
  321.         Procs[pid,"UID"], pid, shortTTY(Procs[pid,"TTY"]),
  322.         basename(Procs[pid,"CMD"]),
  323.         Procs[ppid,"UID"], ppid, shortTTY(Procs[ppid,"TTY"]),
  324.         basename(Procs[ppid,"CMD"])
  325.         }
  326.         else {
  327.         # tty name in Procs[] has already been canonicalized
  328.         if (ShowIdle)
  329.             Procs[pid,"STIME"] = Procs[pid,"TTY"] in IdleTime ? \
  330.             IdleTime[Procs[pid,"TTY"]] : "-"
  331.         ColPrint(makePSline(pid,Procs,Fields))
  332.         }
  333.     }
  334. }
  335.  
  336. ### Start of library routines
  337.  
  338. function max(a,b) {
  339.     if (a > b)
  340.     return a
  341.     else
  342.     return b
  343. }
  344.  
  345. function sec2hms(Seconds,  Hours,Minutes) {
  346.     Hours = int(Seconds / 3600)
  347.     Seconds %= 3600
  348.     Minutes = int(Seconds / 60)
  349.     Seconds %= 60
  350.     return sprintf("%02d:%02d:%02d",Hours,Minutes,Seconds)
  351. }
  352.  
  353. ### Begin ps lib
  354. # getPS 1.1    jhdiii 96/10/09
  355. # 96/02/11    Added Debug flag.
  356. # 96/05/09    Added COMM field.
  357. # 96/05/23    Added selection args, and saving of "ps" PID.
  358. # 96/05/25    Added makePSline()
  359. # 96/10/09    Added RUSER field.
  360.  
  361. # Note: makePSline() needs assign() from array lib.
  362. # to do: generalize based on -o args to 5.0 ps
  363.  
  364. # Do a ps -f and save the output into an array, indexed by pid and field name.
  365. # Input vars:
  366. # Fields: Comma-separated list of fields to put in Procs.
  367. # If Debug is true, debugging info is output.
  368. # selectionArgs may be set to ps options that will report on selected processes
  369. # (e.g. -usomeone -ttty01)
  370. # The default for selectionArgs is -e, which causes information on all
  371. # processes to be recorded.
  372. #
  373. # Output vars:
  374. # PIDs[]: the set of all PIDs seen.
  375. # Also, the element with index "ps" is set to the PID for the ps process.
  376. # Procs[pid,fieldname]: output by field.
  377. #
  378. # Possible fields are:
  379. # UID: User ID; name if available, else number.
  380. # RUSER: Real user ID; name if available, else number.  Only available under
  381. #       5.0, and cannot be requested along with UID.
  382. # PPID: Parent process ID.
  383. # C: CPU scheduling.
  384. # STIME: Start time.  If the start time in the ps output contains a space,
  385. # it is replaced with a "-".  "-" is returned for a defunct process.
  386. # TTY: tty name; may or may not have leading "tty" part.  "-" for defunct proc;
  387. # "?" for proc with no controlling tty.
  388. # TIME: CPU time used.
  389. # CMD: First element of arg vector.
  390. # ARGS: Entire (truncated) arg vector (command + args).
  391. # LINE: Entire ps output line.
  392. # COMM: Process accounting name of process: the name of the executable file,
  393. #       without path.  This is only available under 5.0, and cannot be
  394. #       requested along with CMD or ARGS.
  395. #
  396. # The header line read is also put in Procs with the index "Header".
  397. # The PIDs of the children of each process are put in a comma-separated list
  398. # in Children[pid].
  399. # Return value: the number of processes found, or -2 if an invalid field name
  400. # is passed, or -1 if an error occurs reading from ps.
  401. # Globals: FS is set to " "
  402. #
  403. # ps -f produces output in these forms, under various conditions & releases:
  404. #     UID   PID  PPID  C    STIME     TTY        TIME CMD
  405. #    root 10118 10107  2   Jan-03   ttyp0    00:00:05 -ksh
  406. #    root 10118 10107  2   Jan 03   ttyp0    00:00:05 -ksh
  407. #    root 18197     1  0 08:02:56   ttyp0    00:00:03 /usr/bin/X11/scoterm -geo
  408. function getPS(PIDs,Procs,Fields,Children,Debug,selectionArgs,
  409. stimeI,pidI,ttyI,ppidI,WantLine,psArgs,psSet,newPS,
  410. FieldNames,Wanted,Cmd,getI,Field2Ind,i,Name,Lines,WantArgs,Header,CmdIndex) {
  411.     FS = " "    # magic pattern to reset FS to its default special behaviour
  412.     split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  413.     split("user,pid,ppid,c,stime,tty,time,args",psSet,",")
  414.     Alt["RUSER"] = 1
  415.     Alt["COMM"] = 8
  416.     FieldNames[0] = "LINE"
  417.     for (i in FieldNames)
  418.     Field2Ind[FieldNames[i]] = i
  419.     split(Fields,Wanted,",")
  420.     pidI = Field2Ind["PID"]
  421.     ppidI = Field2Ind["PPID"]
  422.     stimeI = Field2Ind["STIME"]
  423.     ttyI = Field2Ind["TTY"]
  424.     timeI = Field2Ind["TIME"]
  425.     cmdI = Field2Ind["CMD"]
  426.     psArgs = "-f"
  427.     for (i in Wanted) {
  428.     Name = Wanted[i]
  429.     if (Debug)
  430.         printf "Asked for %s\n",Name > "/dev/stderr"
  431.     if (Name == "ARGS")
  432.         WantArgs = 1
  433.     else if (Name == "LINE")
  434.         WantLine = 1
  435.     else if (Name in Alt) {        # New ps fields
  436.         newPS = 1
  437.         psSet[Alt[Name]] = tolower(Name)
  438.         FieldNames[getI[Field2Ind[Name] = Alt[Name]]] = Name
  439.     }
  440.     else if (Name in Field2Ind)
  441.         getI[Field2Ind[Name]]
  442.     else
  443.         return -2
  444.     }
  445.     if (newPS) {
  446.     psArgs = ""
  447.     for (i = 1; i in psSet; i++)
  448.     psArgs = psArgs " -o" psSet[i]
  449.     }
  450.     Lines = 0
  451.     if (selectionArgs == "")
  452.     selectionArgs = "-e"
  453.     Cmd = "echo $$; exec " ( Debug ? "time " : "" ) "/bin/ps " selectionArgs \
  454.     " " psArgs " < /dev/null"
  455.     if ((Cmd | getline PIDs["ps"]) != 1)
  456.     return -1
  457.     if ((Cmd | getline Header) != 1)
  458.     return -1
  459.     Procs["Header"] = Header
  460.     if (!(CmdIndex = index(Header,"CMD")) &&
  461.     !(CmdIndex = index(Header,"COMMAND")))
  462.     return -1
  463.     while ((Cmd | getline) == 1) {
  464.     PIDs[pid = $pidI]
  465.     if (Debug)
  466.         printf "Process %d (%d fields): %s\n",pid,NF,$0 > "/dev/stderr"
  467.     ppid = $ppidI
  468.     if (ppid in Children)
  469.         Children[ppid] = Children[ppid] "," pid
  470.     else
  471.         Children[ppid] = pid
  472.     if (WantArgs)
  473.         Procs[pid,"ARGS"] = substr($0,CmdIndex)
  474.     # Handle this as a special case so that it can be set before the
  475.     # line (possibly) modified
  476.     if (WantLine)
  477.         Procs[pid,"LINE"] = $0
  478.     # Time field with either contain a : (time), a - (new date format),
  479.     # or neither, in which case it occupies 2 fields (old date format).
  480.     if (NF == 6) {    # old ps defunct proc
  481.         # Assign new values to fields, from right to left to avoid
  482.         # overwriting fields before value is moved
  483.         $cmdI = $ttyI
  484.         $timeI = $stimeI
  485.         $ttyI = "-"
  486.         $stimeI = "-"
  487.     }
  488.     if ($stimeI !~ "[-:]") {
  489.         if (!timePos)
  490.         timePos = index($0,$stimeI)
  491.         # Replace space in stime field with "-"
  492.         $0 = substr($0,1,timePos+2) "-" substr($0,timePos+5)
  493.     }
  494.     for (i in getI) {
  495.         Procs[pid,FieldNames[i]] = $i
  496.         if (Debug)
  497.         printf "%s=%s ",FieldNames[i],$i > "/dev/stderr"
  498.     }
  499.     if (Debug)
  500.         print "" > "/dev/stderr"
  501.     Lines++
  502.     }
  503.     close(Cmd)
  504.     return Lines
  505. }
  506.  
  507. # makePSline: generate a line containing desired fields from ps data.
  508. # pid is the ID of the process to generate a line for.
  509. # If a pid of -1 is passed, a header line is returned.
  510. # Procs[] is the ps data, as generated by getPS().
  511. # Fields[] is the set of fields desired in the output, with indexes starting
  512. #    at 1.  The values are field names as e.g. passed to getPS().
  513. # Sep is the separator to put between fields.  If null, a single space is used.
  514. # Return value: a line consisting of the fields requested, in the order of
  515. # their indices in Fields[].
  516. # Example:
  517. # split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  518. # makePSline(pid,psOut,FieldNames)
  519. function makePSline(pid,Procs,Fields,Sep,  i,fieldName,line,width,value) {
  520.     if (Sep == "")
  521.     Sep = " "
  522.     if (!("PID" in _makePSlineWidths))
  523.     # Make TIME before right-adjusted; some versions of ps drop leading
  524.     # 0 fields from it.
  525.     Assign(_makePSlineWidths,
  526.     "UID=-8 PID=5 PPID=5 C=1 STIME=-8 TTY=-4 TIME=8 COMM=-8"," ","=")
  527.     for (i = 1; i in Fields; i++) {
  528.     fieldName = Fields[i]
  529.     if (fieldName in _makePSlineWidths)
  530.         width = _makePSlineWidths[fieldName]
  531.     else
  532.         width = ""
  533.     if (pid == -1)
  534.         value = fieldName
  535.     else if (fieldName == "PID")
  536.         value = pid
  537.     else
  538.         value = Procs[pid,fieldName]
  539.     if (fieldName == "TTY")
  540.         sub("^tty","",value)
  541.     line = line Sep sprintf("%" width "s",value)
  542.     }
  543.     return substr(line,length(Sep)+1)
  544. }
  545.  
  546. ### End ps lib
  547.  
  548. ### start canonTTY library
  549. function nodevTTY(tty) {
  550.     sub("^/dev/","",tty)
  551.     return tty
  552. }
  553.  
  554. function canonTTY(tty) {
  555.     if (tty ~ "^/dev/")
  556.     sub("^/dev/","",tty)
  557.     # Do not change ?, -, and null names for the sake of ps output, etc.
  558.     else if (tty !~ /^tty|^([-?]|)$/)
  559.     tty = "tty" tty
  560.     return tty
  561. }
  562.  
  563. function shortTTY(tty) {
  564.     sub("^/dev/","",tty)
  565.     sub("^tty","",tty)
  566.     return tty
  567. }
  568. ### end canonTTY library
  569. ### Begin utty,id routines
  570.  
  571. # utty: find ttys a user is logged in on.
  572. # For each tty User is logged in on, an element is created in TTYs[].
  573. # The index is the name of the tty, with a leading "/dev/".
  574. # The value is set to 1 if the user is writable on that tty, 0 if not.
  575. # The number of ttys the user is logged in on is returned.
  576. function utty(User,TTYs,  Cmd,Count) {
  577.     Cmd = "exec who -T"
  578.     Count = 0
  579.     while ((Cmd | getline) == 1)
  580.     if ($1 == User) {
  581.         if ($2 == "+")
  582.         TTYs[$3] = 1
  583.         else
  584.         TTYs[$3] = 0
  585.         Count++
  586.     }
  587.     close(Cmd)
  588.     return Count
  589. }
  590.  
  591. # id returns the numeric user id of the user who owns the current process.
  592. # In the array IDs, elements are set as follows:
  593. # uid: numeric user id
  594. # gid: numeric group id
  595. # group: group name, if any
  596. # user: user name, if any
  597. function id(IDs,  Cmd,line,elem) {
  598.     Cmd = "exec id"
  599.     Cmd | getline line
  600.     split(line,elem,"[()=]")
  601.     close(Cmd)
  602.     IDs["user"] = elem[3]
  603.     IDs["gid"] = elem[5]
  604.     IDs["group"] = elem[6]
  605.     return IDs["uid"] = elem[2]
  606. }
  607.  
  608. ### End utty,id routines
  609. ### Begin set library
  610. # 96/05/23 added return values  jhdiii
  611.  
  612. # Return value: the number of new elements added to Inter
  613. function Intersection(A,B,Inter,  Elem,Count) {
  614.     for (Elem in A)
  615.     if (Elem in B && !(Elem in Inter)) {
  616.         Inter[Elem]
  617.         Count++
  618.     }
  619.     return Count
  620. }
  621.  
  622. # Return value: the number of new elements added to Both
  623. function Union(A,B,Both) {
  624.     return CopySet(A,Both) + CopySet(B,Both)
  625. }
  626.  
  627. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  628. # Return value: the number of elements deleted.
  629. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  630.     for (Elem in Subtrahend)
  631.     if (Elem in Minuend) {
  632.         delete Minuend[Elem]
  633.         nDel++
  634.     }
  635.     return nDel
  636. }
  637.  
  638. # Return value: the number of new elements added to To
  639. function CopySet(From,To,  Elem,n) {
  640.     for (Elem in From)
  641.     if (!(Elem in To)) {
  642.         To[Elem]
  643.         n++
  644.     }
  645.     return n
  646. }
  647.  
  648. # Returns 1 if Set is empty, 0 if not.
  649. function IsEmpty(Set,  i) {
  650.     for (i in Set)
  651.     return 0
  652.     return 1
  653. }
  654.  
  655. # MakeSet: make a set from a list.
  656. # An index with the name of each element of the list is created in the given
  657. # array.
  658. # Input variables: 
  659. # Elements is a string containing the list of elements.
  660. # Sep is the character that separates the elements of the list.
  661. # Output variables:
  662. # Set is the array.
  663. # Return value: the number of new elements added to the set.
  664. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  665.     nFound = 0
  666.     Num = split(Elements,Names,Sep)
  667.     for (i = 1; i <= Num; i++) {
  668.     ind = Names[i]
  669.     if (!(ind in Set)) {
  670.         Set[ind]
  671.         nFound++
  672.     }
  673.     }
  674.     return nFound
  675. }
  676.  
  677. # Returns the number of elements in set Set
  678. function NumElem(Set,  elem,Num) {
  679.     for (elem in Set)
  680.     Num++
  681.     return Num
  682. }
  683.  
  684. # Remove all elements from Set
  685. function DeleteAll(Set,  i) {
  686.     split("",Set,",")
  687. }
  688.  
  689. # Returns a list of all of the elements in Set[], with each pair of elements
  690. # separated by Sep.
  691. function set2list(Set,Sep,  list,elem) {
  692.     for (elem in Set)
  693.     list = list Sep elem
  694.     return substr(list,2)    # skip 1st separator
  695. }
  696. ### End set library
  697.  
  698. function basename(path) {
  699.     sub(".*/","",path)
  700.     return path
  701. }
  702.  
  703. ### Begin array routines
  704.  
  705. # InitArr: Initialize an array with values.
  706. # Ind and Vals are separated into lists on Sep.
  707. # For each item in Ind, an index with that name is created in Arr[],
  708. # and the value with the same position in Vals is stored in it.
  709. # Global variables: none.
  710. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  711.     split(Ind,indnames,sep)
  712.     split(Vals,values,sep)
  713.     for (numind in indnames)
  714.     Arr[indnames[numind]] = values[numind]
  715. }
  716.  
  717. function ClearArr(Arr,  Elem) {
  718.     for (Elem in Arr)
  719.     delete Arr[Elem]
  720. }
  721. # Subtract the values in Subtrahend from those in Minuend
  722. function SubtractArr(Minuend,Subtrahend,  Elem) {
  723.     for (Elem in Subtrahend)
  724.     Minuend[Elem] -= Subtrahend[Elem]
  725. }
  726. # For each element of the array In, an element is created in Out having
  727. # an index equal to the value of the element in In and a value equal to 
  728. # the index of the element in In.
  729. function Invert(In,Out,  Index) {
  730.     for (Index in In)
  731.     Out[In[Index]] = Index
  732. }
  733.  
  734. # Assign: make an array from a list of assignments.
  735. # An index with the name of each variable in the list is created in the array.
  736. # Its value is set to the value given for it.
  737. # Input variables: 
  738. # Elements is a string containing the list of variable-value pairs.
  739. # Sep is the string that separates the pairs in the list.
  740. # AssignOp is the string that separates variables from values.
  741. # Output variables:
  742. # Arr is the array.
  743. # Return value: the number of elements added to the set.
  744. # Example:
  745. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  746. function Assign(Arr,Elements,Sep,AssignOp,
  747. Num,Names,Elem,Assignments,Assignment,i) {
  748.     Num = split(Elements,Assignments,Sep)
  749.     for (i = 1; i <= Num; i++) {
  750.     Assignment = Assignments[i]
  751.     Ind = index(Assignment,AssignOp)
  752.     Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
  753.     }
  754.     return Num
  755. }
  756.  
  757. # Packs Arr[], which should have integer indices starting at or above n, to
  758. # contiguous integer indices starting with n.
  759. # If n is not given it defaults to 0.
  760. # Num should be the number of elements in Arr.
  761. function PackArr(Arr,Num,n,  NewInd,OldInd) {
  762.     NewInd = OldInd = n+0
  763.     for (; Num; Num--) {
  764.     while (!(OldInd in Arr))
  765.         OldInd++
  766.     if (NewInd != OldInd) {
  767.         Arr[NewInd] = Arr[OldInd]
  768.         delete Arr[OldInd]
  769.     }
  770.     OldInd++
  771.     NewInd++
  772.     }
  773. }
  774. ### End array routines
  775. ### Begin head-tail routines
  776.  
  777. # @(#) HeadTail.awk 96/05/09
  778. # 95/04/28 Added tail routines.
  779. # 96/05/09 Added all args to HeadTailInit()
  780.  
  781. # Turn on screen-bounded printing.
  782. # Current implementation sets global vars LINES, COLUMNS, LINEGAP, and COLGAP.
  783. # Sets the number of screen lines and rows to Lines and Rows.
  784. # If -1 is passed for either, turns off bounding in that dimension.
  785. # If either is not set or 0 is passed for it, its value is taken from the
  786. # environment, or if not set there, from terminfo, or if not set there, from
  787. # the defaults (24 and 80).
  788. # By default, the other functions in this library leave a "grace space" of
  789. # 1 column and 1 line.  If LineGap or ColGap is passed and is a non-negative
  790. # value, the line gap is set to it.
  791. function HeadTailInit(Lines,Cols,LineGap,ColGap,  Cmd) {
  792.     # tput will use values in environment, but we want to avoid running
  793.     # it if possible.
  794.     if (Cols > 0)
  795.     COLUMNS = Cols
  796.     else if (!Cols)
  797.     if ("COLUMNS" in ENVIRON)
  798.         COLUMNS = ENVIRON["COLUMNS"]
  799.     else {
  800.         Cmd = "exec tput cols"
  801.         Cmd | getline COLUMNS
  802.         close(Cmd)
  803.         if (COLUMNS == "")
  804.         COLUMNS = 80
  805.     }
  806.     if (Lines > 0)
  807.     LINES = Lines
  808.     else if (!Lines)
  809.     if ("LINES" in ENVIRON)
  810.         LINES = ENVIRON["LINES"]
  811.     else {
  812.         Cmd = "exec tput lines"
  813.         Cmd | getline LINES
  814.         close(Cmd)
  815.         if (LINES == "")
  816.         LINES = 24
  817.     }
  818.     LINEGAP = (LineGap != "" && LineGap >= 0) ? LineGap : 1
  819.     COLGAP = (ColGap != "" && ColGap >= 0) ? ColGap : 1
  820. }
  821.  
  822. # Do screen-bound printing.  
  823. # If LINES  is >0, the last LINES-LINEGAP lines are kept in a circular buffer.  
  824. # When TailFlush() is called, they are printed.
  825. # If LINES = 0, all lines are printed immediately.
  826. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  827. # it.
  828. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  829. # saves lines in TailLines[] from 1..LINES-LINEGAP
  830. # Embedded newlines split the line into multiple lines; trailing newlines are
  831. # stripped.  Tabs are expanded to spaces.
  832. function TailPrint(Line) {
  833.     if (!LINES)
  834.     print Line
  835.     else {
  836.     if (++TailPtr > (LINES-LINEGAP))
  837.         TailPtr = 1
  838.     TailLines[TailPtr] = Line
  839.     }
  840. }
  841.  
  842. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  843.     if (!LINES)
  844.     return
  845.     NumPrinted = 0
  846.     PrintLines = LINES-LINEGAP
  847.     # Since lines may contain multiple lines, we must create a buffer to be
  848.     # printed by reading line buffer backwards.
  849.     # Stop when we have copied enough lines, or if we wrap around to the end
  850.     # and find that the entire line buffer was not used.
  851.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  852.     # Split line into individual lines, then process them last to first
  853.     Num = split(TailLines[TailPtr],Lines,"\n")
  854.     for (i = Num; i >= 1; i--) {
  855.         Line = Lines[i]
  856.         if (i == Num && Line == "")    # discard trailing newline
  857.         continue
  858.         # Put this line at the front of the print buffer
  859.         if (COLUMNS)
  860.         Buffer = substr(TabEx(Line),1,COLUMNS - COLGAP) "\n" Buffer
  861.         else
  862.         Buffer = Line "\n" Buffer
  863.         if (++NumPrinted == PrintLines)
  864.         break
  865.     }
  866.     if (!--TailPtr)    # Wrap pointer if neccessary
  867.         TailPtr = PrintLines
  868.     }
  869.     printf "%s",Buffer
  870. }
  871.  
  872. # Do screen-bound printing.  
  873. # If LINES >0, returns 0 when LINES-LINEGAP lines have been printed by
  874. # HeadPrint().  Otherwise returns 1.
  875. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  876. # it.
  877. # Global vars: uses LINES, COLUMNS, LINEGAP, COLGAP; sets/uses LinesPrinted.
  878. # Line should not include newlines.
  879. function HeadPrint(Line) {
  880.     # Check first, in case some calls of this function to not check return
  881.     # value, and in case LINES is 1.
  882.     if (LINES && LinesPrinted >= (LINES-LINEGAP))
  883.     return 0
  884.     if (COLUMNS)
  885.     print substr(Line,1,COLUMNS - COLGAP)
  886.     else
  887.     print Line
  888.     if (LINES && ++LinesPrinted >= (LINES-LINEGAP))
  889.     return 0
  890.     return 1
  891. }
  892.  
  893. function ColPrint(Line) {
  894.     if (COLUMNS)
  895.     print substr(Line,1,COLUMNS - COLGAP)
  896.     else
  897.     print Line
  898.     return 1
  899. }
  900.  
  901. ### End head-tail routines
  902. ### Begin qsort routines
  903.  
  904. # Arr[] is an array of values with arbitrary indices.
  905. # k[] is returned with numeric indices 1..n.
  906. # The values in k[] are the indices of Arr[],
  907. # ordered so that if Arr[] is stepped through
  908. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  909. # through in order of the values of its elements.
  910. # The return value is the number of elements in the arrays (n).
  911. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  912.     ElNum = 0
  913.     for (ArrInd in Arr)
  914.     k[++ElNum] = ArrInd
  915.     qsortSegment(Arr,k,1,ElNum)
  916.     return ElNum
  917. }
  918.  
  919. # Sort a segment of an array.
  920. # Arr[] contains data with arbitrary indices.
  921. # k[] has indices 1..nelem, with the indices of arr[] as values.
  922. # This function sorts the elements of arr that are pointed to by
  923. # k[start..end], swapping the values of elements of k[] so that
  924. # when this function returns arr[k[start..end]] will be in order.
  925. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  926.     # handle two-element case explicitly for a tiny speedup
  927.     if ((end - start) == 1) {
  928.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  929.         k[start] = tmpe
  930.         k[end] = tmps
  931.     }
  932.     return
  933.     }
  934.     # Make sure comparisons act on these as numbers
  935.     left = start+0
  936.     right = end+0
  937.     sepval = Arr[k[int((left + right) / 2)]]
  938.     # Make every element <= sepval be to the left of every element > sepval
  939.     while (left < right) {
  940.     while (Arr[k[left]] < sepval)
  941.         left++
  942.     while (Arr[k[right]] > sepval)
  943.         right--
  944.     if (left < right) {
  945.         tmp = k[left]
  946.         k[left++] = k[right]
  947.         k[right--] = tmp
  948.     }
  949.     }
  950.     if (left == right)
  951.     if (Arr[k[left]] < sepval)
  952.         left++
  953.     else
  954.         right--
  955.     if (start < right)
  956.     qsortSegment(Arr,k,start,right)
  957.     if (left < end)
  958.     qsortSegment(Arr,k,left,end)
  959. }
  960.  
  961. # Arr[] is an array of values with arbitrary indices.
  962. # k[] is returned with numeric indices 1..n.
  963. # The values in k are the indices of Arr[],
  964. # ordered so that if Arr[] is stepped through
  965. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  966. # through in order of the values of its indices.
  967. # The return value is the number of elements in the arrays (n).
  968. # If the indexes are numeric, Numeric should be true, so that they can be
  969. # compared as such rather than as strings.  Numeric indexes do not have to be
  970. # contiguous.
  971. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  972.     ElNum = 0
  973.     if (Numeric)
  974.     # Indexes do not preserve numeric type, so must be forced
  975.     for (ArrInd in Arr)
  976.         k[++ElNum] = ArrInd+0
  977.     else
  978.     for (ArrInd in Arr)
  979.         k[++ElNum] = ArrInd
  980.     qsortNumIndByValue(k,1,ElNum)
  981.     return ElNum
  982. }
  983.  
  984. # Arr is an array of elements with contiguous numeric indexes to be sorted
  985. # by value.
  986. # start and end are the starting and ending indexes of the range to be sorted.
  987. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  988.     # handle two-element case explicitly for a tiny speedup
  989.     if ((start - end) == 1) {
  990.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  991.         Arr[start] = tmpe
  992.         Arr[end] = tmps
  993.     }
  994.     return
  995.     }
  996.     left = start+0
  997.     right = end+0
  998.     sepval = Arr[int((left + right) / 2)]
  999.     while (left < right) {
  1000.     while (Arr[left] < sepval)
  1001.         left++
  1002.     while (Arr[right] > sepval)
  1003.         right--
  1004.     if (left <= right) {
  1005.         tmp = Arr[left]
  1006.         Arr[left++] = Arr[right]
  1007.         Arr[right--] = tmp
  1008.     }
  1009.     }
  1010.     if (start < right)
  1011.     qsortNumIndByValue(Arr,start,right)
  1012.     if (left < end)
  1013.     qsortNumIndByValue(Arr,left,end)
  1014. }
  1015.  
  1016. ### End qsort routines
  1017. ### Start of ProcArgs library
  1018. # @(#) ProcArgs 1.12 97/02/22
  1019. # 92/02/29 john h. dubois iii (john@armory.com)
  1020. # 93/07/18 Added "#" arg type
  1021. # 93/09/26 Do not count -h against MinArgs
  1022. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  1023. #          Removed meaning of "+" or "-" by itself.
  1024. # 94/03/08 Added & option and *()< option types.
  1025. # 94/04/02 Added NoRCopt to Opts()
  1026. # 94/06/11 Mark numeric variables as such.
  1027. # 94/07/08 Opts(): Do not require any args if h option is given.
  1028. # 95/01/22 Record options given more than once.  Record option num in argv.
  1029. # 95/06/08 Added ExclusiveOptions().
  1030. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  1031. #          Expand $VARNAME at the start of its filenames.
  1032. #          Let varname=0 and -option- turn off an option.
  1033. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  1034. #          of the vars should be searched for in the environment.
  1035. #          Check for duplicate rcfiles.
  1036. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  1037. #          now return various negatives values on error, not just -1, and
  1038. #          Opts() may set Err to various positive values, not just 1.
  1039. #          Added AllowUnrecOpt.
  1040. # 96/05/23 Check type given for & option
  1041. # 96/06/15 Re-port to awk
  1042. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  1043. #          used by other functions.
  1044. # 96/10/15 Added OptChars
  1045. # 96/11/01 Added exOpts arg to Opts()
  1046. # 96/11/16 Added ; type
  1047. # 96/12/08 Added Opt2Set() & Opt2Sets()
  1048. # 96/12/27 Added CmdLineOpt()
  1049. # 97/02/22 Remove packed elements.
  1050. # 97/02/28 Make sequence # for rcfiles & environ be "f" and "e".
  1051. #          Replaced CmdLineOpt() with OptsGiven().
  1052.  
  1053. # optlist is a string which contains all of the possible command line options.
  1054. # A character followed by certain characters indicates that the option takes
  1055. # an argument, with type as follows:
  1056. # :    String argument
  1057. # ;    Non-empty string argument
  1058. # *    Floating point argument
  1059. # (    Non-negative floating point argument
  1060. # )    Positive floating point argument
  1061. # #    Integer argument
  1062. # <    Non-negative integer argument
  1063. # >    Positive integer argument
  1064. # The only difference the type of argument makes is in the runtime argument
  1065. # error checking that is done.
  1066.  
  1067. # The & option is a special case used to get numeric options without the
  1068. # user having to give an option character.  It is shorthand for [-+.0-9].
  1069. # If & is included in optlist and an option string that begins with one of
  1070. # these characters is seen, the value given to "&" will include the first
  1071. # char of the option.  & must be followed by a type character other than ":"
  1072. # or ";".
  1073. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  1074.  
  1075. # Strings in argv[] which begin with "-" or "+" are taken to be
  1076. # strings of options, except that a string which consists solely of "-"
  1077. # or "+" is taken to be a non-option string; like other non-option strings,
  1078. # it stops the scanning of argv and is left in argv[].
  1079. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  1080. # If an option takes an argument, the argument may either immediately
  1081. # follow it or be given separately.
  1082. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  1083. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  1084. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  1085. # this feature had a flaw that caused problems in some cases.  See the OptChars
  1086. # parameter to explicitly set the option-specifier characters.
  1087.  
  1088. # If an option that does not take an argument is given,
  1089. # an index with its name is created in Options and its value is set to the
  1090. # number of times it occurs in argv[].
  1091.  
  1092. # If an option that does take an argument is given, an index with its name is
  1093. # created in Options and its value is set to the value of the argument given
  1094. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  1095. # If an option that takes an argument is given more than once,
  1096. # Options[option-name,"count"] is incremented, and the value is assigned to
  1097. # the index (option-name,instance) where instance is 2 for the second occurance
  1098. # of the option, etc.
  1099. # In other words, the first time an option with a value is encountered, the
  1100. # value is assigned to an index consisting only of its name; for any further
  1101. # occurances of the option, the value index has an extra (count) dimension.
  1102.  
  1103. # The sequence number for each option found in argv[] is stored in
  1104. # Options[option-name,"num",instance], where instance is 1 for the first
  1105. # occurance of the option, etc.  The sequence number starts at 1 and is
  1106. # incremented for each option, both those that have a value and those that
  1107. # do not.  Options set from a config file get a sequence number of "f", and
  1108. # options set in the environment get a sequence number of "e".
  1109.  
  1110. # Options and their arguments are deleted from argv.
  1111. # Note that this means that there may be gaps left in the indices of argv[].
  1112. # If compress is nonzero, argv[] is packed by moving its elements so that
  1113. # they have contiguous integer indices starting with 0.
  1114. # Option processing will stop with the first unrecognized option, just as
  1115. # though -- was given except that unlike -- the unrecognized option will not be
  1116. # removed from ARGV[].  Normally, an error value is returned in this case.
  1117. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  1118. # be found, so the number of remaining arguments is returned instead.
  1119. # If OptChars is not a null string, it is the set of characters that indicate
  1120. # that an argument is an option string if the string begins with one of the
  1121. # characters.  A string consisting solely of two of the same option-indicator
  1122. # characters stops the scanning of argv[].  The default is "-+".
  1123. # argv[0] is not examined.
  1124. # The number of arguments left in argc is returned.
  1125. # If an error occurs, the global string OptErr is set to an error message
  1126. # and a negative value is returned.
  1127. # Current error values:
  1128. # -1: option that required an argument did not get it.
  1129. # -2: argument of incorrect type supplied for an option.
  1130. # -3: unrecognized (invalid) option.
  1131. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  1132. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  1133. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  1134. {
  1135. # ArgNum is the index of the argument being processed.
  1136. # ArgsLeft is the number of arguments left in argv.
  1137. # Arg is the argument being processed.
  1138. # ArgLen is the length of the argument being processed.
  1139. # ArgInd is the position of the character in Arg being processed.
  1140. # Option is the character in Arg being processed.
  1141. # Pos is the position in OptList of the option being processed.
  1142. # NumOpt is true if a numeric option may be given.
  1143.     ArgsLeft = argc
  1144.     NumOpt = index(OptList,"&")
  1145.     OptionNum = 0
  1146.     if (OptChars == "")
  1147.     OptChars = "-+"
  1148.     while (OptChars != "") {
  1149.     c = substr(OptChars,1,1)
  1150.     OptChars = substr(OptChars,2)
  1151.     OptCharSet[c]
  1152.     OptTerm[c c]
  1153.     }
  1154.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  1155.     Arg = argv[ArgNum]
  1156.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  1157.         break    # Not an option; quit
  1158.     if (Arg in OptTerm) {
  1159.         delete argv[ArgNum]
  1160.         ArgsLeft--
  1161.         break
  1162.     }
  1163.     ArgLen = length(Arg)
  1164.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  1165.         Option = substr(Arg,ArgInd,1)
  1166.         if (NumOpt && Option ~ /[-+.0-9]/) {
  1167.         # If this option is a numeric option, make its flag be & and
  1168.         # its option string flag position be the position of & in
  1169.         # the option string.
  1170.         Option = "&"
  1171.         Pos = NumOpt
  1172.         # Prefix Arg with a char so that ArgInd will point to the
  1173.         # first char of the numeric option.
  1174.         Arg = "&" Arg
  1175.         ArgLen++
  1176.         }
  1177.         # Find position of flag in option string, to get its type (if any).
  1178.         # Disallow & as literal flag.
  1179.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  1180.         if (AllowUnrecOpt) {
  1181.             Escape = 1
  1182.             break
  1183.         }
  1184.         else {
  1185.             OptErr = "Invalid option: " specGiven Option
  1186.             return -3
  1187.         }
  1188.         }
  1189.  
  1190.         # Find what the value of the option will be if it takes one.
  1191.         # NeedNextOpt is true if the option specifier is the last char of
  1192.         # this arg, which means that if the option requires a value it is
  1193.         # the next arg.
  1194.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  1195.         if (GotValue = ArgNum + 1 < argc)
  1196.             Value = argv[ArgNum+1]
  1197.         }
  1198.         else {    # Value is included with option
  1199.         Value = substr(Arg,ArgInd + 1)
  1200.         GotValue = 1
  1201.         }
  1202.  
  1203.         if (HadValue = AssignVal(Option,Value,Options,
  1204.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  1205.         specGiven)) {
  1206.         if (HadValue < 0)    # error occured
  1207.             return HadValue
  1208.         if (HadValue == 2)
  1209.             ArgInd++    # Account for the single-char value we used.
  1210.         else {
  1211.             if (NeedNextOpt) {    # option took next arg as value
  1212.             delete argv[++ArgNum]
  1213.             ArgsLeft--
  1214.             }
  1215.             break    # This option has been used up
  1216.         }
  1217.         }
  1218.     }
  1219.     if (Escape)
  1220.         break
  1221.     # Do not delete arg until after processing of it, so that if it is not
  1222.     # recognized it can be left in ARGV[].
  1223.     delete argv[ArgNum]
  1224.     ArgsLeft--
  1225.     }
  1226.     if (compress != 0) {
  1227.     dest = 1
  1228.     src = argc - ArgsLeft + 1
  1229.     if (src != dest) {
  1230.         for (count = ArgsLeft - 1; count; count--) {
  1231.         ARGV[dest] = ARGV[src]
  1232.         dest++
  1233.         src++
  1234.         }
  1235.         for (; dest < src; dest++)
  1236.         delete ARGV[dest]
  1237.     }
  1238.     }
  1239.     return ArgsLeft
  1240. }
  1241.  
  1242. # Assignment to values in Options[] occurs only in this function.
  1243. # Option: Option specifier character.
  1244. # Value: Value to be assigned to option, if it takes a value.
  1245. # Options[]: Options array to return values in.
  1246. # ArgType: Argument type specifier character.
  1247. # GotValue: Whether any value is available to be assigned to this option.
  1248. # Name: Name of option being processed.
  1249. # OptionNum: Number of this option (starting with 1) if set in argv[],
  1250. #     or 0 if it was given in a config file or in the environment.
  1251. # SingleOpt: true if the value (if any) that is available for this option was
  1252. #     given as part of the same command line arg as the option.  Used only for
  1253. #     options from the command line.
  1254. # specGiven is the option specifier character use, if any (e.g. - or +),
  1255. # for use in error messages.
  1256. # Global variables: OptErr
  1257. # Return value: negative value on error, 0 if option did not require an
  1258. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  1259. # the arg.
  1260. # Current error values:
  1261. # -1: Option that required an argument did not get it.
  1262. # -2: Value of incorrect type supplied for option.
  1263. # -3: Bad type given for option &
  1264. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  1265. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  1266.     # If option takes a value...    [
  1267.     NumTypes = "*()#<>]"
  1268.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  1269.     OptErr = "Bad type given for & option"
  1270.     return -3
  1271.     }
  1272.  
  1273.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  1274.     if (!GotValue) {
  1275.         if (Name != "")
  1276.         OptErr = "Variable requires a value -- " Name
  1277.         else
  1278.         OptErr = "option requires an argument -- " Option
  1279.         return -1
  1280.     }
  1281.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1282.         OptErr = Err
  1283.         return -2
  1284.     }
  1285.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1286.     if (ArgType != ":" && ArgType != ";")
  1287.         Value += 0
  1288.     if ((Instance = ++Options[Option,"count"]) > 1)
  1289.         Options[Option,Instance] = Value
  1290.     else
  1291.         Options[Option] = Value
  1292.     }
  1293.     # If this is an environ or rcfile assignment & it was given a value...
  1294.     else if (!OptionNum && Value != "") {
  1295.     UsedValue = 1
  1296.     # If the value is "0" or "-" and this is the first instance of it,
  1297.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1298.     # turn off an option (for the simple "Option in Options" test) in such
  1299.     # a way that it cannot be turned on in a later file.
  1300.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1301.         Instance = 1
  1302.     else
  1303.         Instance = ++Options[Option]
  1304.     # Save the value even though this is a flag
  1305.     Options[Option,Instance] = Value
  1306.     }
  1307.     # If this is a command line flag and has a - following it in the same arg,
  1308.     # it is being turned off.
  1309.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1310.     UsedValue = 2
  1311.     if (Option in Options)
  1312.         Instance = ++Options[Option]
  1313.     else
  1314.         Instance = 1
  1315.     Options[Option,Instance]
  1316.     }
  1317.     # If this is a flag assignment without a value, increment the count for the
  1318.     # flag unless it was turned off.  The indicator for a flag being turned off
  1319.     # is that the flag index has not been set in Options[] but it has an
  1320.     # instance count.
  1321.     else if (Option in Options || !((Option,1) in Options))
  1322.     # Increment number of times this flag seen; will inc null value to 1
  1323.     Instance = ++Options[Option]
  1324.     Options[Option,"num",Instance] = OptionNum
  1325.     return UsedValue
  1326. }
  1327.  
  1328. # Option is the option letter
  1329. # Value is the value being assigned
  1330. # Name is the var name of the option, if any
  1331. # ArgType is one of:
  1332. # :    String argument
  1333. # ;    Non-null string argument
  1334. # *    Floating point argument
  1335. # (    Non-negative floating point argument
  1336. # )    Positive floating point argument
  1337. # #    Integer argument
  1338. # <    Non-negative integer argument
  1339. # >    Positive integer argument
  1340. # specGiven is the option specifier character use, if any (e.g. - or +),
  1341. # for use in error messages.
  1342. # Returns null on success, err string on error
  1343. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1344.     if (ArgType == ":")
  1345.     return ""
  1346.     if (ArgType == ";") {
  1347.     if (Value == "")
  1348.         Err = "must be a non-empty string"
  1349.     }
  1350.     # A number begins with optional + or -, and is followed by a string of
  1351.     # digits or a decimal with digits before it, after it, or both
  1352.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1353.     Err = "must be a number"
  1354.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1355.     Err = "may not include a fraction"
  1356.     else if (ArgType ~ "[()<>]" && Value < 0)
  1357.     Err = "may not be negative"
  1358.     # (
  1359.     else if (ArgType ~ "[)>]" && Value == 0)
  1360.     Err = "must be a positive number"
  1361.     if (Err != "") {
  1362.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1363.     if (Name != "")
  1364.         return ErrStr "variable " substr(Name,1,1) " " Err
  1365.     else {
  1366.         if (Option == "&")
  1367.         Option = Value
  1368.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1369.     }
  1370.     }
  1371.     else
  1372.     return ""
  1373. }
  1374.  
  1375. # Note: only the above functions are needed by ProcArgs.
  1376. # The rest of these functions call ProcArgs() and also do other
  1377. # option-processing stuff.
  1378.  
  1379. # Opts: Process command line arguments.
  1380. # Opts processes command line arguments using ProcArgs()
  1381. # and checks for errors.  If an error occurs, a message is printed
  1382. # and the program is exited.
  1383. #
  1384. # Input variables:
  1385. # Name is the name of the program, for error messages.
  1386. # Usage is a usage message, for error messages.
  1387. # OptList the option description string, as used by ProcArgs().
  1388. # MinArgs is the minimum number of non-option arguments that this
  1389. # program should have, non including ARGV[0] and +h.
  1390. # If the program does not require any non-option arguments,
  1391. # MinArgs should be omitted or given as 0.
  1392. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1393. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1394. # by the value of the environment variable HOME.  If a filename begins with
  1395. # $, the part from the character after the $ up until (but not including)
  1396. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1397. # environment; if found its value will be substituted, if not the filename will
  1398. # be discarded.
  1399. # rcfiles are read in the order given.
  1400. # Values given in them will not override values given on the command line,
  1401. # and values given in later files will not override those set in earlier
  1402. # files, because AssignVal() will store each with a different instance index.
  1403. # The first instance of each variable, either on the command line or in an
  1404. # rcfile, will be stored with no instance index, and this is the value
  1405. # normally used by programs that call this function.
  1406. # VarNames is a comma-separated list of variable names to map to options,
  1407. # in the same order as the options are given in OptList.
  1408. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1409. # searched for in the environment.  If set to -1, all values will be searched
  1410. # for in the environment.  Values given in the environment will override
  1411. # those given in the rcfiles but not those given on the command line.
  1412. # NoRCopt, if given, is an additional letter option that if given on the
  1413. # command line prevents the rcfiles and environment from being read.
  1414. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1415. # ExclusiveOptions() for a description of exOpts.
  1416. # Special options:
  1417. # If x is made an option and is given, some debugging info is output.
  1418. # h is assumed to be the help option.
  1419.  
  1420. # Global variables:
  1421. # The command line arguments are taken from ARGV[].
  1422. # The arguments that are option specifiers and values are removed from
  1423. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1424. # The number of elements in ARGV[] should be in ARGC.
  1425. # After processing, ARGC is set to the number of elements left in ARGV[].
  1426. # The option values are put in Options[].
  1427. # On error, Err is set to a positive integer value so it can be checked for in
  1428. # an END block.
  1429. # Return value: The number of elements left in ARGV is returned.
  1430. # Must keep OptErr global since it may be set by InitOpts().
  1431. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1432. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1433.     if (MinArgs == "")
  1434.     MinArgs = 0
  1435.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1436.     optChars)
  1437.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1438.     if (ArgsLeft >= 0) {
  1439.         OptErr = "Not enough arguments"
  1440.         Err = 4
  1441.     }
  1442.     else
  1443.         Err = -ArgsLeft
  1444.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1445.     Name,OptErr,Usage > "/dev/stderr"
  1446.     exit 1
  1447.     }
  1448.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1449.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1450.     {
  1451.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1452.     Err = -e
  1453.     exit 1
  1454.     }
  1455.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1456.     {
  1457.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1458.     Err = 1
  1459.     exit 1
  1460.     }
  1461.     return ArgsLeft
  1462. }
  1463.  
  1464. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1465. # <variable-name><assignment-char><value>.
  1466. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1467. # line and whitespace between the variable name and the assignment character) 
  1468. # is stripped.  Lines that do not contain an assignment operator or which
  1469. # contain a null variable name are ignored, other than possibly being noted in
  1470. # the return value.  If more than one assignment is made to a variable, the
  1471. # first assignment is used.
  1472. # Input variables:
  1473. # File is the file to read.
  1474. # Comment is the line-comment character.  If it is found as the first non-
  1475. #     whitespace character on a line, the line is ignored.
  1476. # Assign is the assignment string.  The first instance of Assign on a line
  1477. #     separates the variable name from its value.
  1478. # If StripWhite is true, whitespace around the value (whitespace between the
  1479. #     assignment char and trailing whitespace on the line) is stripped.
  1480. # VarPat is a pattern that variable names must match.  
  1481. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1482. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1483. #     a line; no assignment operator is needed.  These variables are set in
  1484. #     the output array with a null value.  Lines containing nothing but
  1485. #     whitespace are still ignored.
  1486. # Output variables:
  1487. # Values[] contains the assignments, with the indexes being the variable names
  1488. #     and the values being the assigned values.
  1489. # Lines[] contains the line number that each variable occured on.  A flag set
  1490. #     is record by giving it an index in Lines[] but not in Values[].
  1491. # Return value:
  1492. # If any errors occur, a string consisting of descriptions of the errors
  1493. # separated by newlines is returned.  In no case will the string start with a
  1494. # numeric value.  If no errors occur,  the number of lines read is returned.
  1495. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1496. FlagsOK,
  1497. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1498.     if (Comment != "")
  1499.     Comment = "^" Comment
  1500.     AssignLen = length(Assign)
  1501.     if (VarPat == "")
  1502.     VarPat = "."    # null varname not allowed
  1503.     while ((Status = (getline Line < File)) == 1) {
  1504.     LineNum++
  1505.     sub("^[ \t]+","",Line)
  1506.     if (Line == "")        # blank line
  1507.         continue
  1508.     if (Comment != "" && Line ~ Comment)
  1509.         continue
  1510.     if (Pos = index(Line,Assign)) {
  1511.         Var = substr(Line,1,Pos-1)
  1512.         Val = substr(Line,Pos+AssignLen)
  1513.         if (StripWhite) {
  1514.         sub("^[ \t]+","",Val)
  1515.         sub("[ \t]+$","",Val)
  1516.         }
  1517.     }
  1518.     else {
  1519.         Var = Line    # If no value, var is entire line
  1520.         Val = ""
  1521.     }
  1522.     if (!FlagsOK && Val == "") {
  1523.         Errs = Errs \
  1524.         sprintf("\nBad assignment on line %d of file %s: %s",
  1525.         LineNum,File,Line)
  1526.         continue
  1527.     }
  1528.     sub("[ \t]+$","",Var)
  1529.     if (Var !~ VarPat) {
  1530.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1531.         LineNum,File,Var)
  1532.         continue
  1533.     }
  1534.     if (!(Var in Lines)) {
  1535.         Lines[Var] = LineNum
  1536.         if (Pos)
  1537.         Values[Var] = Val
  1538.     }
  1539.     }
  1540.     if (Status)
  1541.     Errs = Errs "\nCould not read file " File
  1542.     close(File)
  1543.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1544. }
  1545.  
  1546. # Variables:
  1547. # Data is stored in Options[].
  1548. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1549. # Global vars:
  1550. # Sets OptErr.  Uses ENVIRON[].
  1551. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1552. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1553. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1554. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1555.     split("",filesRead,"")    # make awk know this is an array
  1556.     NumVars = split(VarNames,Vars,",")
  1557.     TypesInd = Ret = 0
  1558.     if (EnvSearch == -1)
  1559.     EnvSearch = NumVars
  1560.     for (i = 1; i <= NumVars; i++) {
  1561.     Var = Vars[i]
  1562.     CharOpt = substr(OptList,++TypesInd,1)
  1563.     if (CharOpt ~ "^[:;*()#<>&]$")
  1564.         CharOpt = substr(OptList,++TypesInd,1)
  1565.     Map[Var] = CharOpt
  1566.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1567.     # Do not overwrite entries from environment
  1568.     if (i <= EnvSearch && Var in ENVIRON &&
  1569.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,"e")) < 0)
  1570.         return Err
  1571.     }
  1572.  
  1573.     numrcFiles = split(rcFiles,fNames,":")
  1574.     for (i = 1; i <= numrcFiles; i++) {
  1575.     rcFile = fNames[i]
  1576.     if (rcFile ~ "^~/")
  1577.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1578.     else if (rcFile ~ /^\$/) {
  1579.         rcFile = substr(rcFile,2)
  1580.         match(rcFile,"^[a-zA-Z0-9_]*")
  1581.         envvar = substr(rcFile,1,RLENGTH)
  1582.         if (envvar in ENVIRON)
  1583.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1584.         else
  1585.         continue
  1586.     }
  1587.     if (rcFile in filesRead)
  1588.         continue
  1589.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1590.     # may be the same
  1591.     filesRead[rcFile]
  1592.     if ("x" in Options)
  1593.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1594.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1595.     if (retStr > 0)
  1596.         READ_RCFILE = 1
  1597.     else if (ret != "") {
  1598.         OptErr = retStr
  1599.         Ret = -1
  1600.     }
  1601.     for (Var in Lines)
  1602.         if (Var in Map) {
  1603.         if ((Err = AssignVal(Map[Var],Var in Values ? Values[Var] : "",
  1604.         Options,Types[Var],Var in Values,Var,"f")) < 0)
  1605.             return Err
  1606.         }
  1607.         else {
  1608.         OptErr = sprintf(\
  1609.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1610.         Lines[Var],rcFile)
  1611.         Ret = -1
  1612.         }
  1613.     }
  1614.  
  1615.     if ("x" in Options)
  1616.     for (Var in Map)
  1617.         if (Map[Var] in Options)
  1618.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1619.         "/dev/stderr"
  1620.         else
  1621.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1622.     return Ret
  1623. }
  1624.  
  1625. # OptSets is a semicolon-separated list of sets of option sets.
  1626. # Within a list of option sets, the option sets are separated by commas.  For
  1627. # each set of sets, if any option in one of the sets is in Options[] AND any
  1628. # option in one of the other sets is in Options[], an error string is returned.
  1629. # If no conflicts are found, nothing is returned.
  1630. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1631. # the exclusions presented by the first set of sets (ab,def,g) if:
  1632. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1633. # (a or b is in Options[]) AND (g is in Options) OR
  1634. # (d, e, or f is in Options[]) AND (g is in Options)
  1635. # An error will be returned due to the exclusions presented by the second set
  1636. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1637. # todo: make options given on command line unset options given in config file
  1638. # todo: that they conflict with.
  1639. function ExclusiveOptions(OptSets,Options,
  1640. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1641. SetNum,OSetNum) {
  1642.     NumSetSets = split(OptSets,SetSets,";")
  1643.     # For each set of sets...
  1644.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1645.     # NumSets is the number of sets in this set of sets.
  1646.     NumSets = split(SetSets[SetSet],Sets,",")
  1647.     # For each set in a set of sets except the last...
  1648.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1649.         s1 = Sets[SetNum]
  1650.         L1 = length(s1)
  1651.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1652.         # If any of the options in this set was given, check whether
  1653.         # any of the options in the other sets was given.  Only check
  1654.         # later sets since earlier sets will have already been checked
  1655.         # against this set.
  1656.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1657.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1658.             s2 = Sets[OSetNum]
  1659.             L2 = length(s2)
  1660.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1661.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1662.                 ErrStr = ErrStr "\n"\
  1663.                 sprintf("Cannot give both %s and %s options.",
  1664.                 c1,c2)
  1665.             }
  1666.     }
  1667.     }
  1668.     if (ErrStr != "")
  1669.     return substr(ErrStr,2)
  1670.     return ""
  1671. }
  1672.  
  1673. # The value of each instance of option Opt that occurs in Options[] is made an
  1674. # index of Set[].
  1675. # The return value is the number of instances of Opt in Options.
  1676. function Opt2Set(Options,Opt,Set,  count) {
  1677.     if (!(Opt in Options))
  1678.     return 0
  1679.     Set[Options[Opt]]
  1680.     count = Options[Opt,"count"]
  1681.     for (; count > 1; count--)
  1682.     Set[Options[Opt,count]]
  1683.     return count
  1684. }
  1685.  
  1686. # The value of each instance of option Opt that occurs in Options[] that
  1687. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1688. # Other values are made indexes of Set[].
  1689. # The return value is the number of instances of Opt in Options.
  1690. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1691.     ret = Opt2Set(Options,Opt,aSet)
  1692.     for (value in aSet)
  1693.     if (substr(value,1,1) == "!")
  1694.         nSet[substr(value,2)]
  1695.     else
  1696.         Set[value]
  1697.     return ret
  1698. }
  1699.  
  1700. # Returns true if any option in the string Opts was given, as indicated by the
  1701. # data in Options[]. If any of Arg, Env, or File are true, the given opts are
  1702. # only considered to have been set if they were set in the command line
  1703. # arguments, environment, or in a configuration file, respectively. 
  1704. function OptsGiven(Options,Opts,Arg,Env,File,  l,i,Opt,j,c) {
  1705.     if (!Arg && !Env && !File)
  1706.     Arg = Env = File = 1
  1707.     l = length(Opts)
  1708.     for (i = 1; i <= l; i++) {
  1709.     Opt = substr(Opts,i,1)
  1710.     for (j = 1; (Opt,"num",j) in Options; j++) {
  1711.         c = Options[Opt,"num",j]
  1712.         if (Arg && c+0 > 0 || File && c == "f" || Env && c == "e")
  1713.         return 1
  1714.     }
  1715.     }
  1716.     return 0
  1717. }
  1718. ### End of ProcArgs library
  1719. # Put a list of login shells (from /etc/shells) into set LoginShells[].
  1720. # Returns -1 if /etc/shells could not be read, else the number of shells found.
  1721. function ReadShells(LoginShells,  ret,Num,Line) {
  1722.     while (ret = ((getline Line < "/etc/shells") == 1))
  1723.     if (Line ~ "^/") {
  1724.         Num++
  1725.         sub(/[ \t]+/,"",Line)
  1726.         LoginShells[Line]
  1727.     }
  1728.     close("/etc/shells")
  1729.     _DidReadShells = 1
  1730.     return ret ? -1 : Num
  1731. }
  1732.  
  1733. # Makes array shellUser[] have an index for each user who has a shell in
  1734. # /etc/shells.
  1735. # Returns 1 on success, 0 if there is a problem reading /etc/shells or
  1736. # /etc/passwd.
  1737. function makeShellUser(shellUser,  LoginShells,ret,oFS) {
  1738.     if (ReadShells(LoginShells) < 0)
  1739.     return 0
  1740.     oFS = FS
  1741.     FS = ":"
  1742.     while (ret = ((getline < "/etc/passwd") == 1))
  1743.     if ($7 in LoginShells)
  1744.         shellUser[$1]
  1745.     close("/etc/passwd")
  1746.     FS = oFS
  1747.     return !ret
  1748. }
  1749.